/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package TimeRecording.reporting;

import TimeRecording.business.DaisyChainSystem;
import TimeRecording.business.DaisyChainUtilities;
import TimeRecording.business.DateRange;
import TimeRecording.tables.Employee;
import java.awt.Image;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import net.sf.jasperreports.engine.util.JRLoader;
import net.sf.jasperreports.view.JasperViewer;

/**
 *
 * @author david
 */
public class CalendarReport {

    private final static String SINGLE_WEEK_REPORT =
            "/reports/absence/OneWeekAbsenceReport.jasper";

    private final static String SINGLE_MONTH_REPORT =
            "/reports/absence/OneMonthAbsenceReport.jasper";

    private final static String SINGLE_YEAR_REPORT =
            "/reports/absence/OneYearAbsenceReport.jasper";

    private final static Logger LOGGER =
            Logger.getLogger(CalendarReport.class.getName());

    private GregorianCalendar getMonthCalendar = new GregorianCalendar();
    
    // <editor-fold defaultstate="collapsed" desc="WeekDetail">
    public static class WeekDetail {

        private String employee;

        private Image monday;
        private Image tuesday;
        private Image wednesday;
        private Image thursday;
        private Image friday;
        private Image saturday;
        private Image sunday;

        private Integer monDate;
        private Integer tuesDate;
        private Integer wednesDate;
        private Integer thursDate;
        private Integer friDate;
        private Integer saturDate;
        private Integer sunDate;

        public WeekDetail() {
        }

        public WeekDetail(String employee,
                Image monday, Image tuesday, Image wednesday, Image thursday,
                Image friday, Image saturday, Image sunday,
                Integer monDate, Integer tuesDate, Integer wednesDate, Integer thursDate,
                Integer friDate, Integer saturDate, Integer sunDate) {

            this.employee = employee;
            
            this.monday = monday;
            this.tuesday = tuesday;
            this.wednesday = wednesday;
            this.thursday = thursday;
            this.friday = friday;
            this.saturday = saturday;
            this.sunday = sunday;

            this.monDate = monDate;
            this.tuesDate = tuesDate;
            this.wednesDate = wednesDate;
            this.thursDate = thursDate;
            this.friDate = friDate;
            this.saturDate = saturDate;
            this.sunDate = sunDate;
        }

        public String getEmployee() {
            return employee;
        }

        public void setEmployee(String employee) {
            this.employee = employee;
        }

        public Integer getFriDate() {
            return friDate;
        }

        public void setFriDate(Integer friDate) {
            this.friDate = friDate;
        }

        public Image getFriday() {
            return friday;
        }

        public void setFriday(Image friday) {
            this.friday = friday;
        }

        public Integer getMonDate() {
            return monDate;
        }

        public void setMonDate(Integer monDate) {
            this.monDate = monDate;
        }

        public Image getMonday() {
            return monday;
        }

        public void setMonday(Image monday) {
            this.monday = monday;
        }

        public Integer getSaturDate() {
            return saturDate;
        }

        public void setSaturDate(Integer saturDate) {
            this.saturDate = saturDate;
        }

        public Image getSaturday() {
            return saturday;
        }

        public void setSaturday(Image saturday) {
            this.saturday = saturday;
        }

        public Integer getSunDate() {
            return sunDate;
        }

        public void setSunDate(Integer sunDate) {
            this.sunDate = sunDate;
        }

        public Image getSunday() {
            return sunday;
        }

        public void setSunday(Image sunday) {
            this.sunday = sunday;
        }

        public Integer getThursDate() {
            return thursDate;
        }

        public void setThursDate(Integer thursDate) {
            this.thursDate = thursDate;
        }

        public Image getThursday() {
            return thursday;
        }

        public void setThursday(Image thursday) {
            this.thursday = thursday;
        }

        public Integer getTuesDate() {
            return tuesDate;
        }

        public void setTuesDate(Integer tuesDate) {
            this.tuesDate = tuesDate;
        }

        public Image getTuesday() {
            return tuesday;
        }

        public void setTuesday(Image tuesday) {
            this.tuesday = tuesday;
        }

        public Integer getWednesDate() {
            return wednesDate;
        }

        public void setWednesDate(Integer wednesDate) {
            this.wednesDate = wednesDate;
        }

        public Image getWednesday() {
            return wednesday;
        }

        public void setWednesday(Image wednesday) {
            this.wednesday = wednesday;
        }

    } // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="MonthDetail">
    public class MonthDetail {

        private String title;
        private JRBeanCollectionDataSource weeks;

        public MonthDetail() {
        }

        public MonthDetail(String title, JRBeanCollectionDataSource weeks) {
            this.title = title;
            this.weeks = weeks;
        }

        public String getTitle() {
            return title;
        }

        public void setTitle(String title) {
            this.title = title;
        }

        public JRBeanCollectionDataSource getWeeks() {
            return weeks;
        }

        public void setWeeks(JRBeanCollectionDataSource weeks) {
            this.weeks = weeks;
        }

    } // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="YearDetail">
    public class YearDetail {

        private String title;
        private JRBeanCollectionDataSource months;

        public YearDetail() {
        }

        public YearDetail(String title, JRBeanCollectionDataSource months) {
            this.title = title;
            this.months = months;
        }

        public String getTitle() {
            return title;
        }

        public void setTitle(String title) {
            this.title = title;
        }

        public JRBeanCollectionDataSource getMonths() {
            return months;
        }

        public void setMonths(JRBeanCollectionDataSource months) {
            this.months = months;
        }

    } // </editor-fold>

    /**
     * Displays a single week absence report containing a row for each of the
     * supplied employees.  The title of the report reflects the date range.
     *
     * @param range date range for the calendar week
     * @param iconProvider the icon provider (maps absences to icons)
     * @param entityManager the connection through which to query the database
     */
    @SuppressWarnings("unchecked")
    public void singleWeekAbsenceReport(DateRange week,
            CalendarIconProvider iconProvider, EntityManager entityManager) {
        
        List<Employee> employees = Employee.getActiveEmployees(entityManager);
        singleWeekAbsenceReport(week, iconProvider, employees);
    }

    /**
     * Displays a single week absence report for all non-deleted employees.  The
     * The title of the report reflects the date range.
     *
     * @param range date range for the calendar week
     * @param iconProvider the icon provider (maps absences to icons)
     * @param employeeS the employeeS to interrogate
     */
    @SuppressWarnings("unchecked")
    public void singleWeekAbsenceReport(DateRange week,
            CalendarIconProvider iconProvider, Collection<Employee> employees) {

        Map<String, Object> parameters =
                new ReportingUtilities().getBasicParameters();

        String title = "Absences for w/c " + DaisyChainUtilities.
                dateAsString(week.getFromDate());
        parameters.put("TITLE", title);

        viewAbsenceData(
                getWeeks(employees, week, iconProvider),
                parameters, SINGLE_WEEK_REPORT);
    }

    /**
     * Displays a single month absence report for all non-deleted employees.
     * The report may run over several pages with each page containing the
     * details for up to 12 employees.  The title of the report reflects the
     * date range and the title on each month identifies the employee.
     *
     * @param month date range for the calendar month
     * @param iconProvider the icon provider (maps absences to icons)
     * @param entityManager the connection through which to query the database
     */
    @SuppressWarnings("unchecked")
    public void singleMonthAbsenceReport(DateRange month,
            CalendarIconProvider iconProvider, EntityManager entityManager) {

        List<Employee> employees = Employee.getActiveEmployees(entityManager);
        singleMonthAbsenceReport(month, iconProvider, employees);
    }

    /**
     * Displays a single month absence report for each of the supplied
     * employees.  The report may run over several pages with each page
     * containing the details for up to 12 employees.  The title of the report
     * reflects the date range and the title on each month identifies the
     * employee.
     *
     * @param month date range for the calendar month
     * @param iconProvider the icon provider (maps absences to icons)
     * @param employees the employees to interrogate
     */
    @SuppressWarnings("unchecked")
    public void singleMonthAbsenceReport(DateRange month,
            CalendarIconProvider iconProvider, Collection<Employee> employees) {

        Map<String, Object> parameters =
                new ReportingUtilities().getBasicParameters();

        String title = "Absences for Month " + DaisyChainUtilities.
                monthAsString(month.getFromDate());
        parameters.put("TITLE", title);

        viewAbsenceData(
                getMonths(employees, month, iconProvider),
                parameters, SINGLE_MONTH_REPORT);
    }

    /**
     * Displays an annual absence report for all non-deleted employees.  Each
     * report is displayed on its own page with the title of the page
     * encompassing the employee's name.
     *
     * @param range date range for the calendar year
     * @param iconProvider the icon provider (maps absences to icons)
     * @param entityManager the connection through which to query the database
     */
    @SuppressWarnings("unchecked")
    public void singleYearAbsenceReport(DateRange range,
            CalendarIconProvider iconProvider, EntityManager entityManager) {

        List<Employee> employees = Employee.getActiveEmployees(entityManager);
        singleYearAbsenceReport(range, iconProvider, employees);
    }

    /**
     * Displays an annual absence report for each of the supplied employees.
     * Each report is displayed on its own page with the title of the page
     * encompassing the employee's name.  Note that the particular Java syntax
     * used allows for the passing of a single employee record.
     *
     * @param range date range for the calendar year
     * @param iconProvider the icon provider (maps absences to icons)
     * @param employee the employee(s) to interrogate
     */
    @SuppressWarnings("unchecked")
    public void singleYearAbsenceReport(DateRange range,
            CalendarIconProvider iconProvider, Employee... employee) {

        singleYearAbsenceReport(range, iconProvider, Arrays.asList(employee));
    }

    /**
     * Displays an annual absence report for each of the supplied employees.
     * Each report is displayed on its own page with the title of the page
     * encompassing the employee's name.
     *
     * @param employees the employees to interrogate
     * @param range date range for the calendar year
     * @param iconProvider the icon provider (maps absences to icons)
     */
    @SuppressWarnings("unchecked")
    public void singleYearAbsenceReport(DateRange range,
            CalendarIconProvider iconProvider, Collection<Employee> employees) {

        Map<String, Object> parameters =
                new ReportingUtilities().getBasicParameters();

        String title = "Absences for year " + DaisyChainUtilities.
                yearAsString(range.getFromDate());
        parameters.put("TITLE", title);

        viewAbsenceData(
                getYears(employees, range, iconProvider),
                parameters, SINGLE_YEAR_REPORT);
    }

    /**
     * Fetches an array list of complete annual absence records, one for each of
     * the supplied employees.
     * 
     * @param employee the employee to interrogate
     * @param range date range for the calendar year
     * @param iconProvider the icon provider (maps absences to icons)
     * @return
     */
    private ArrayList<YearDetail> getYears(Collection<Employee> employees,
            DateRange range, CalendarIconProvider iconProvider) {

        ArrayList<YearDetail> years = new ArrayList<YearDetail>();

        for (Employee employee : employees) {
            years.add(getYear(employee, range, iconProvider));
        }

        return years;
    }

    /**
     * This method gets the details (suitable for calendar display) of all
     * absences for the employee for the month commencing at the start of the
     * supplied date range.  Any days that fall outside of the supplied date
     * range result in an effectively empty slot for calendar display.
     *
     * @param employee the employee to interrogate
     * @param range date range for the calendar year
     * @param iconProvider the icon provider (maps absences to icons)
     * @return a week's worth of calendar details
     */
    private YearDetail getYear (Employee employee,
            DateRange range, CalendarIconProvider iconProvider) {

        String employeeName = employee.getFriendlyName();

        ArrayList<MonthDetail> months = new ArrayList<MonthDetail>();

        // Iterate through months
        ArrayList<DateRange> monthlyDates =
                DaisyChainUtilities.getFullMonths(range);

        for (DateRange datesForMonth : monthlyDates) {
            months.add(getMonth(employee, datesForMonth, iconProvider));
        }

        return new YearDetail(employeeName, new JRBeanCollectionDataSource(months));
    }

    /**
     * Fetches a monthly absence record for each of the employees in the
     * collection.  The title of each month is given set as the employee,
     * overriding the default behaviour.
     *
     * @param employees a collection of employees
     * @param range date range for the calendar month
     * @param iconProvider the icon provider (maps absences to icons)
     * @return an array list of monthly absence records, once for each employee
     */
    private ArrayList<MonthDetail> getMonths(Collection<Employee> employees,
            DateRange range, CalendarIconProvider iconProvider) {

        ArrayList<MonthDetail> months = new ArrayList<MonthDetail>();

        for (Employee employee : employees) {
            MonthDetail month = getMonth(employee, range, iconProvider);
            month.setTitle(employee.getFriendlyName());
            months.add(month);
        }

        return months;
    }

    /**
     * This method gets the details (suitable for calendar display) of all
     * absences for the employee for the month commencing at the start of the
     * supplied date range.  Any days that fall outside of the supplied date
     * range result in an effectively empty slot for calendar display.  By
     * default, the month's title is the name of the month.
     *
     * @param employee the employee to interrogate
     * @param range date range for the calendar month
     * @param date the start of the week
     * @param iconProvider the icon provider (maps absences to icons)
     * @return a week's worth of calendar details
     */
    private MonthDetail getMonth (Employee employee,
            DateRange range, CalendarIconProvider iconProvider) {

        String monthName = DaisyChainUtilities.monthAsString(range.getFromDate());

        ArrayList<WeekDetail> weeks = new ArrayList<WeekDetail>();

        // Iterate through weeks
        ArrayList<DateRange> weeklyDates =
                DaisyChainUtilities.getFullWeeks(range);

        for (DateRange datesForWeek : weeklyDates) {
            weeks.add(getWeek(employee, range, datesForWeek.getFromDate(), iconProvider));
        }

        return new MonthDetail(monthName, new JRBeanCollectionDataSource(weeks));
    }

    /**
     * Returns an array list of weekly absence records for the specified
     * employees for the specified week.
     *
     * @param employees a collection of employees
     * @param range date range for the week
     * @param iconProvider the icon provider (maps absences to icons)
     * @return an array list of weekly absence records, one for each employee
     */
    private ArrayList<WeekDetail> getWeeks(Collection<Employee> employees,
            DateRange range, CalendarIconProvider iconProvider) {
        
        ArrayList<WeekDetail> weeks = new ArrayList<WeekDetail>();
        
        for (Employee employee : employees) {
            weeks.add(getWeek(employee, range, range.getFromDate(), iconProvider));
        }
        
        return weeks;
    }

    /**
     * This method gets the details (suitable for calendar display) of all
     * absences for the employee for the week commencing on the supplied date.
     * Any days that fall outside of the supplied date range result in an
     * effectively empty slot for calendar display.  The supplied date range
     * may specify the start/end of the entire calendar month.
     *
     * @param employee the employee to interrogate
     * @param range the clipping date range
     * @param date the start of the week (the Monday)
     * @param iconProvider the icon provider (maps absences to icons)
     * @return a week's worth of calendar details
     */
    private WeekDetail getWeek(Employee employee, DateRange range,
            Date date, CalendarIconProvider iconProvider) {

        // Begin at the start of the week (assuming the given date is a Monday)

        date = DaisyChainSystem.cleanDate(date);
        GregorianCalendar calendar = new GregorianCalendar();
        calendar.setTime(date);

        // Note that the month is driven by the range; this is because when
        // displaying 12 months worth of data we will often encounter the same
        // week as part of two separate months.  On such an occassion we need to
        // drive the month by the clipping range being used.

        GregorianCalendar monthCalendar = new GregorianCalendar();
        monthCalendar.setTime(range.getFromDate());

        // Now, rattle through the 7 days of the week; any day that falls
        // outside of our date range will have "empty" details returned by the
        // helper methods.

        Image monday = iconProvider.getIcon(employee, range, calendar.getTime());
        Integer monDate = dayOfMonth(calendar, range);

        calendar.add(Calendar.DATE, 1);
        Image tuesday = iconProvider.getIcon(employee, range, calendar.getTime());
        Integer tuesDate = dayOfMonth(calendar, range);

        calendar.add(Calendar.DATE, 1);
        Image wednesday = iconProvider.getIcon(employee, range, calendar.getTime());
        Integer wednesDate = dayOfMonth(calendar, range);

        calendar.add(Calendar.DATE, 1);
        Image thursday = iconProvider.getIcon(employee, range, calendar.getTime());
        Integer thursDate = dayOfMonth(calendar, range);

        calendar.add(Calendar.DATE, 1);
        Image friday = iconProvider.getIcon(employee, range, calendar.getTime());
        Integer friDate = dayOfMonth(calendar, range);

        calendar.add(Calendar.DATE, 1);
        Image saturday = iconProvider.getIcon(employee, range, calendar.getTime());
        Integer saturDate = dayOfMonth(calendar, range);

        calendar.add(Calendar.DATE, 1);
        Image sunday = iconProvider.getIcon(employee, range, calendar.getTime());
        Integer sunDate = dayOfMonth(calendar, range);

        return new WeekDetail(employee.getFriendlyName(),
                monday, tuesday, wednesday, thursday, friday, saturday, sunday,
                monDate, tuesDate, wednesDate, thursDate, friDate, saturDate, sunDate);
    }

    /**
     * As long as the supplied date falls within the supplied date range, this
     * method returns the day of the week as an object integer; if it falls
     * outside of the date range then it returns null.
     *
     * @param calendar a calendar object set to a specific date
     * @param range a range against which the calendar can be bounds checked
     * @return the day of the month or null if out of range
     */
    private Integer dayOfMonth(Calendar calendar, DateRange range) {
        Date date = calendar.getTime();
        if (date.before(range.getFromDate()) || date.after(range.getToDate())) {
            return null;
        } else {
            return calendar.get(Calendar.DAY_OF_MONTH);
        }
    }

    /**
     * Executes the lowest level of our report generation... give it the data
     * and the class and let it get on with it.  Does very little other than
     * wrap the Jasper calls in an try... catch... block.
     *
     * @param data the report data
     * @param reportClass the report class
     */
    @SuppressWarnings("unchecked")
    private void viewAbsenceData(ArrayList data, Map parameters, String reportClass) {

        if (parameters == null) {
            parameters = new ReportingUtilities().getBasicParameters();
        }

        try {
            JasperReport template = (JasperReport)JRLoader.loadObject(
                    getClass().getResource(reportClass));

            JasperPrint report = JasperFillManager.fillReport(
                    template, parameters, new JRBeanCollectionDataSource(data));

            JasperViewer.viewReport(report, false);

        } catch (JRException ex) {
            Logger.getLogger(CalendarReport.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}